# main.py - 电商爬虫工具（带自动更新的GUI版本）
from logger import app_logger as logger
import customtkinter as ctk
from tkinter import messagebox, ttk
import requests
import shutil
import sys
import subprocess
import tempfile
from pathlib import Path
import threading
import ctypes
# ================== 配置区 ==================
UPDATE_CONFIG = {
    "version_url": "http://115.120.195.74/latest.json",  # 替换为你的服务器地址
    "timeout": 5,
}

APP_ROOT = Path(__file__).parent if not getattr(sys, 'frozen', False) else Path(sys.executable).parent
CURRENT_EXE = Path(sys.executable)
TEMP_NEW_EXE = CURRENT_EXE.with_name(CURRENT_EXE.name + ".new")
BACKUP_EXE = CURRENT_EXE.with_name(CURRENT_EXE.name + ".bak")


def is_admin():
    """检查当前进程是否具有管理员权限"""
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        return False


def clean_old_mei_tempdirs():
    """清理上次 PyInstaller 运行残留的 _MEI 临时目录（增强版）"""
    try:
        temp_dir = Path(tempfile.gettempdir())
        for item in temp_dir.iterdir():
            if item.is_dir() and item.name.startswith("_MEI"):
                if hasattr(sys, '_MEIPASS') and str(item) == sys._MEIPASS:
                    continue
                try:
                    shutil.rmtree(item)
                    logger.info(f"成功清理残留临时目录: {item}")
                except PermissionError:
                    logger.debug(f"权限拒绝，跳过清理: {item}（可能正被占用）")
                except Exception as e:
                    logger.debug(f"清理 {item} 时出错: {e}")
    except Exception as e:
        logger.warning(f"清理临时目录时发生异常: {e}")


def resource_path(relative_path):
    """ 获取 PyInstaller 打包后的资源路径 """
    try:
        base_path = sys._MEIPASS
    except AttributeError:
        base_path = Path(__file__).parent
    return Path(base_path) / relative_path


def get_current_version():
    """从 version.txt 读取当前版本"""
    try:
        ver_file = resource_path("../version.txt")
        logger.info(f"正在从路径读取版本: {ver_file}")
        if ver_file.exists():
            with open(ver_file, 'r', encoding='utf-8') as f:
                ver = f.read().strip()
                logger.info(f"成功读取本地版本: {ver}")
                return ver
        else:
            logger.warning("version.txt 不存在，使用默认版本 1.0.0")
            return "1.0.0"
    except Exception as e:
        logger.error(f"读取 version.txt 异常: {e}", exc_info=True)
        return "1.0.0"


def get_remote_version():
    """获取远程最新版本"""
    try:
        logger.info(f"正在请求远程版本信息: {UPDATE_CONFIG['version_url']}")
        resp = requests.get(UPDATE_CONFIG["version_url"], timeout=UPDATE_CONFIG["timeout"], allow_redirects=False)
        if 300 <= resp.status_code < 400:
            logger.warning(f"服务器返回重定向: {resp.status_code} -> {resp.headers.get('Location')}")
            raise Exception("服务器重定向到 HTTPS，不支持")
        resp.raise_for_status()
        data = resp.json()
        version = data.get("version")
        url = data.get("url")
        changelog = data.get("changelog", "")
        logger.info(f"获取远程版本成功: {version}")
        return version, url, changelog
    except Exception as e:
        logger.error(f"获取远程版本失败: {e}", exc_info=True)
        return None, None, ""


def download_new_version(download_url, progress_callback=None):
    """下载新版本（支持进度回调）"""
    try:
        logger.info(f"开始下载新版本: {download_url}")
        logger.info(f"临时保存路径: {TEMP_NEW_EXE}")

        with requests.get(download_url, stream=True) as r:
            r.raise_for_status()
            total_size = int(r.headers.get('content-length', 0))
            downloaded = 0

            with open(TEMP_NEW_EXE, 'wb') as f:
                for chunk in r.iter_content(8192):
                    f.write(chunk)
                    downloaded += len(chunk)
                    if progress_callback and total_size > 0:
                        progress_callback(downloaded, total_size)

        # 🔍【增强容错】确认文件已成功写入且大小合理
        if not TEMP_NEW_EXE.exists():
            raise FileNotFoundError("下载完成但临时文件不存在")

        actual_size = TEMP_NEW_EXE.stat().st_size
        if total_size > 0 and actual_size < total_size * 0.9:
            raise RuntimeError(f"文件不完整：期望 {total_size} 字节，实际 {actual_size}")

        logger.info("新版本下载完成")
        return True

    except Exception as e:
        logger.error(f"下载新版本失败: {e}", exc_info=True)
        # 尝试清理残缺文件
        try:
            if TEMP_NEW_EXE.exists():
                TEMP_NEW_EXE.unlink()
                logger.info("已清理残缺的临时下载文件")
        except Exception as clean_err:
            logger.warning(f"清理临时文件失败: {clean_err}")
        return False


def replace_and_restart():
    logger.info("开始执行程序替换与重启流程")

    current_executable = Path(sys.executable).resolve()
    new_executable = TEMP_NEW_EXE
    backup_executable = current_executable.with_suffix('.exe.bak')

    # 检查是否已经是管理员
    if not is_admin():
        logger.warning("当前不是管理员权限，尝试提权重启...")
        try:
            # 使用 runas 提权启动自己（带参数）
            cmd = [
                'runas', '/user:Administrator',
                str(current_executable), '--update-mode'
            ]
            subprocess.Popen(cmd)
            sys.exit(0)  # 退出当前进程
        except Exception as e:
            logger.error(f"提权失败: {e}")
            messagebox.showerror("更新失败", "需要管理员权限才能更新，请手动以管理员身份运行程序。")
            return

    # 如果已经是管理员，则继续正常替换
    if not new_executable.exists():
        logger.critical("致命错误：新版本临时文件不存在，无法更新！")
        messagebox.showerror("更新失败", "新版本文件缺失，请重试。")
        return

    try:
        if current_executable.exists():
            shutil.copy2(current_executable, backup_executable)
            logger.info(f"原程序已备份至: {backup_executable}")
    except Exception as e:
        logger.error(f"备份失败: {e}")
        messagebox.showerror("更新警告", "备份失败，但仍将继续更新。")

    try:
        shutil.move(str(new_executable), str(current_executable))
        logger.info(f"新程序已安装至: {current_executable}")
    except PermissionError as e:
        logger.error(f"权限拒绝：{e}")
        messagebox.showerror("更新失败", "无法覆盖当前程序，请以管理员身份运行后重试。")
        return
    except Exception as e:
        logger.error(f"替换失败: {e}")
        messagebox.showerror("更新失败", "无法更新程序，请检查权限。")
        return

    # 成功替换后，启动新实例
    try:
        subprocess.Popen([str(current_executable)])
    except Exception as e:
        logger.error(f"启动新实例失败: {e}")
        messagebox.showerror("启动失败", "无法启动新版本，请手动双击图标运行。")

    sys.exit(0)


class UpdateDialog(ctk.CTkToplevel):
    """更新提示弹窗"""

    def __init__(self, parent, remote_ver, changelog, download_url, overlay_frame):
        super().__init__(parent)
        self.parent = parent
        self.download_url = download_url
        self.overlay_frame = overlay_frame
        self.title("发现新版本")
        self.geometry("400x300")
        self.resizable(False, False)
        self.transient(parent)
        self.grab_set()

        ctk.CTkLabel(self, text=f"发现新版本 {remote_ver}", font=("Microsoft YaHei", 16, "bold")).pack(pady=10)
        ctk.CTkLabel(self, text=f"当前版本: {get_current_version()}").pack(pady=2)

        log_frame = ctk.CTkFrame(self, height=100)
        log_frame.pack(fill="x", padx=20, pady=10)
        log_text = ctk.CTkTextbox(log_frame, height=80)
        log_text.insert("end", changelog)
        log_text.configure(state="disabled")
        log_text.pack(padx=5, pady=5, fill="both", expand=True)

        self.progress_var = ctk.DoubleVar(value=0)
        self.progress_bar = ttk.Progressbar(self, variable=self.progress_var, maximum=100, mode='determinate')
        self.progress_label = ctk.CTkLabel(self, text="")

        btn_frame = ctk.CTkFrame(self)
        btn_frame.pack(pady=10)

        ctk.CTkButton(btn_frame, text="取消", width=80, command=self.cancel).grid(row=0, column=0, padx=5)
        self.update_btn = ctk.CTkButton(btn_frame, text="立即更新", width=80, command=self.start_download)
        self.update_btn.grid(row=0, column=1, padx=5)

    def start_download(self):
        self.update_btn.configure(state="disabled", text="下载中...")
        self.progress_bar.pack(padx=20, pady=5, fill="x")
        self.progress_label.pack(pady=2)
        threading.Thread(target=self._download_thread, daemon=True).start()

    def _download_thread(self):
        def progress(downloaded, total):
            percent = (downloaded / total) * 100
            self.progress_var.set(percent)
            self.progress_label.configure(text=f"下载中... {percent:.1f}%")
            self.update()

        if download_new_version(self.download_url, progress):
            self.progress_label.configure(text="下载完成，正在安装...")
            self._hide_overlay_and_destroy()
            self.after(500, replace_and_restart)
        else:
            messagebox.showerror("更新失败", "下载失败，请稍后重试。", parent=self)
            self._hide_overlay_and_destroy()

    def _hide_overlay_and_destroy(self):
        """隐藏遮罩并销毁弹窗"""
        self.overlay_frame.place_forget()
        self.destroy()

    def cancel(self):
        self._hide_overlay_and_destroy()


def check_for_update_gui(root, overlay_frame):
    """在后台线程检查更新，并在主线程弹窗"""

    def task():
        current_ver = get_current_version()
        logger.info(f"当前版本: {current_ver}")

        remote_ver, download_url, changelog = get_remote_version()
        if not remote_ver or not download_url:
            logger.warning("无法获取远程版本信息，跳过更新检查")
            return

        def parse_version(v):
            try:
                return tuple(map(int, v.split('.')))
            except Exception as ve:
                logger.error(f"版本号格式错误: '{v}' -> {ve}")
                return (0, 0, 0)

        if parse_version(remote_ver) > parse_version(current_ver):
            logger.info(f"发现新版本 {remote_ver}，准备弹窗提示")
            # 显示遮罩
            overlay_frame.place(x=0, y=0, relwidth=1, relheight=1)
            # 在主线程弹窗
            root.after(0, lambda: UpdateDialog(root, remote_ver, changelog, download_url, overlay_frame))
        else:
            logger.info("已是最新版本，无需更新")

    threading.Thread(target=task, daemon=True).start()


# ================== 主程序界面 ==================
def main_program():
    try:
        logger.info("========== 程序启动 ==========")
        clean_old_mei_tempdirs()
        app = ctk.CTk()
        app.title("数据采集工具")
        app.geometry("600x400")

        # === 新增：创建全局遮罩层（用于置灰主窗口）===
        overlay_frame = ctk.CTkFrame(
            app,
            fg_color="#aaaaaa",  # 灰色
            bg_color="#aaaaaa",
            corner_radius=0
        )
        # 初始隐藏
        overlay_frame.place_forget()

        title = ctk.CTkLabel(app, text="数据采集工具更新", font=("Microsoft YaHei", 20, "bold"))
        title.pack(pady=20)

        version_label = ctk.CTkLabel(app, text=f"版本: {get_current_version()}")
        version_label.pack(pady=5)

        status_label = ctk.CTkLabel(app, text="状态: 就绪", text_color="green")
        status_label.pack(pady=10)
        # 👇 新增：显示资源路径（调试用，上线可删）
        debug_path = resource_path("../version.txt")
        debug_label = ctk.CTkLabel(app, text=f"资源路径: {debug_path}", font=("Courier", 8), text_color="gray")
        debug_label.pack(pady=2)

        def start_crawling():
            status_label.configure(text="状态: 爬虫运行中...", text_color="blue")
            logger.info("用户点击【开始爬取】按钮")
            # TODO: 你的爬虫逻辑

        ctk.CTkButton(app, text="开始爬取", command=start_crawling).pack(pady=20)

        # 注册窗口关闭事件
        def on_closing():
            logger.info("程序即将退出，执行清理...")
            app.destroy()

        app.protocol("WM_DELETE_WINDOW", on_closing)

        # 启动更新检查（传入遮罩）
        check_for_update_gui(app, overlay_frame)

        app.mainloop()

        clean_old_mei_tempdirs()

    except Exception as e:
        logger.critical("主程序发生未处理异常", exc_info=True)
        messagebox.showerror("严重错误", "程序崩溃，请查看 logs/app.log 获取详情。")
    finally:
        clean_old_mei_tempdirs()


# ================== 程序入口 ==================
if __name__ == '__main__':
    main_program()
    logger.info("========== 程序结束 ==========")